《Linux操作系统 您所在的位置:网站首页 linux usb摄像头驱动 《Linux操作系统

《Linux操作系统

#《Linux操作系统| 来源: 网络整理| 查看: 265

目录 隐藏 5.1启用linux内核对usb摄像头的支持 5.2 V4L2拍照应用实现 5.2.1 V4L2拍照原理 5.2.2编译测试 欢迎访问我的网站 欢迎订阅我的微信公众号

开发环境: 主机:Ubuntu 18.04 开发板:OK3568-C开发板

Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。本文将基于V4L2使用usb摄像头(UVC)拍照。

5.1启用linux内核对usb摄像头的支持

1.配置内核 进入内核目录,配置linux内核

$ make ARCH=arm64 menuconfig

2.启用摄像头支持

最后一步时根据自己需要进行选择摄像头配置。

Linux 4.19 Device Drivers ---> Multimedia support ---> [*] Cameras/video grabbers support [*] Media usb adapters----> USB video class (uvc)

XzK0mQ.md.png

XzKBwj.md.png

Device Drivers ---> [*]usb support [*] usb announce new device

XzKDTs.md.png

【注】选项框内为星号*表示开启并编译进内核,空白表示不开启,M表示开启并编译为模块

3.编译 修改完后,可以开始编译linux源码 执行以下命令:

退出后将修改内容保存到配置文件:

$ make ARCH=arm64 savedefconfig $ mv defconfig arch/arm64/configs/OK3568-C-linux_defconfig

回到 SDK 根目录进行编译:

# 选择板型配置文件 $./build.sh BoardConfig-ok3568.mk # 编译内核 ./build.sh kernel

这样就把 UVC 编译进内核,当然也可把 UVC 编译成模块。

当插入UVC摄像头就会有相应的设备。

XzKskn.md.png

如果插入多个摄像头,设备名后缀数字依次增加,如: video1 video2 video3。

5.2 V4L2拍照应用实现 5.2.1 V4L2拍照原理

在Linux下,所有外设都被看成一种特殊的文件,也就是一切皆文件,Linux中所有的外设均可像访问普通文件一样对其进行读写操作。

V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。

在Linux中V4L2拍照的调用过程如下图所示。

XzKyYq.md.png

V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集。

主要分为五个步骤:

首先,打开设备文件,参数初始化,通过V4L2接口设置图像的采集窗口、采集的点阵大小和格式。 其次,申请若干图像采集的帧缓冲区,便于应用程序读取/处理视频数据。 第三,将申请到的帧缓冲区在数据采集输入队列排队,并启动图片采集。 第四,驱动开始图像数据的采集,应用程序从数据采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入数据采集输入队列,循环往复采集连续的数据; 第五,停止数据采集。

完整代码如下:

【usb_camera.c】

/** ****************************************************************************** * @file usb_camera.c * @author BruceOu * @version V1.0 * @date 2022-04-24 * @blog https://blog.bruceou.cn/ * @Official Accounts 嵌入式实验楼 * @brief USB CAMERA ****************************************************************************** */ /**Includes*********************************************************************/ #include "usb_camera.h" #define DEBUG /**【全局变量声明】*************************************************************/ buffer *user_buf = NULL; static unsigned int n_buffer = 0; static unsigned long file_length; char picture_name[20] ="rk_picture"; int num = 0; /** * @brief 打开摄像头设备函数 * @param None * @retval fd 摄像头设备 */ int open_camer_device(char * videoDev) { int fd; /*1.打开设备文件。*/ if((fd = open(videoDev,O_RDWR | O_NONBLOCK)) < 0) { perror("Fail to open"); pthread_exit(NULL); } return fd; } /** * @brief 初始化视频设备函数 * @param fd 摄像头设备 * @retval */ int init_camer_device(int fd) { struct v4l2_fmtdesc fmt; struct v4l2_capability cap; struct v4l2_format stream_fmt; int ret; /*2.取得设备的capability,查询视频设备驱动的功能 比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability*/ ret = ioctl(fd,VIDIOC_QUERYCAP,&cap); if(ret < 0) { perror("FAIL to ioctl VIDIOC_QUERYCAP"); exit(EXIT_FAILURE); } //判断是否是一个视频捕捉设备 if(!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE)) { perror("The Current device is not a video capture device\n"); exit(EXIT_FAILURE); } //判断是否支持视频流形式 if(!(cap.capabilities & V4L2_CAP_STREAMING)) { perror("The Current device does not support streaming i/o\n"); exit(EXIT_FAILURE); } /*3.设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。*/ memset(&fmt,0,sizeof(fmt)); fmt.index = 0; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; while((ret = ioctl(fd,VIDIOC_ENUM_FMT,&fmt)) == 0) { fmt.index ++ ; #ifdef DEBUG printf("{pixelformat = %c%c%c%c},description = '%s'\n", fmt.pixelformat & 0xff,(fmt.pixelformat >> 8)&0xff, (fmt.pixelformat >> 16) & 0xff,(fmt.pixelformat >> 24)&0xff, fmt.description); #endif } //设置摄像头采集数据格式,如设置采集数据的 //长,宽,图像格式(JPEG,YUYV,MJPEG等格式) stream_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE stream_fmt.fmt.pix.width = 680;//宽,必须是16的倍数 stream_fmt.fmt.pix.height = 480;//高,必须是16的倍数 stream_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//视频数据存储类型//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV; stream_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; //设置当前驱动的频捕获格式 if(-1 == ioctl(fd,VIDIOC_S_FMT,&stream_fmt)) { perror("Fail to ioctl"); exit(EXIT_FAILURE); } //计算图片大小 file_length = stream_fmt.fmt.pix.bytesperline * stream_fmt.fmt.pix.height; //初始化视频采集方式(mmap) init_mmap(fd); return 0; } /** * @brief 初始化视频采集方式(mmap) * @param fd 摄像头设备 * @retval */ int init_mmap(int fd) { int i = 0; struct v4l2_requestbuffers reqbuf; /*4.向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers*/ bzero(&reqbuf,sizeof(reqbuf)); reqbuf.count = 4;//缓存数量,也就是说在缓存队列里保持多少张照片 reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; reqbuf.memory = V4L2_MEMORY_MMAP;//或V4L2_MEMORY_USERPTR //申请视频缓冲区(这个缓冲区位于内核空间,需要通过mmap映射) //这一步操作可能会修改reqbuf.count的值,修改为实际成功申请缓冲区个数 if(-1 == ioctl(fd,VIDIOC_REQBUFS,&reqbuf)) { perror("Fail to ioctl 'VIDIOC_REQBUFS'"); exit(EXIT_FAILURE); } n_buffer = reqbuf.count; #ifdef DEBUG printf("n_buffer = %d\n",n_buffer); #endif user_buf = calloc(reqbuf.count,sizeof(*user_buf));//内存中建立对应空间 if(user_buf == NULL) { fprintf(stderr,"Out of memory\n"); exit(EXIT_FAILURE); } /*5.将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了, 而不必去复制。mmap*/ for(i = 0; i < n_buffer; i ++) { struct v4l2_buffer buf;//驱动中的一帧 bzero(&buf,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; //查询申请到内核缓冲区的信息 if(-1 == ioctl(fd,VIDIOC_QUERYBUF,&buf)) //映射用户空间 { perror("Fail to ioctl : VIDIOC_QUERYBUF"); exit(EXIT_FAILURE); } user_buf[i].length = buf.length; user_buf[i].start = mmap( NULL,/*start anywhere*/ buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,buf.m.offset//通过mmap建立映射关系,返回映射区的起始地址 ); if(MAP_FAILED == user_buf[i].start) { perror("Fail to mmap"); exit(EXIT_FAILURE); } } return 0; } int start_capturing(int fd) { unsigned int i; enum v4l2_buf_type type; /*6.将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer*/ for(i = 0;i < n_buffer;i ++) { struct v4l2_buffer buf; bzero(&buf,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; //把数据从缓存中读取出来 if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))//申请到的缓冲进入列队 { perror("Fail to ioctl 'VIDIOC_QBUF'"); exit(EXIT_FAILURE); } } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /*7.开始视频的采集。VIDIOC_STREAMON*/ if(-1 == ioctl(fd,VIDIOC_STREAMON,&type)) //开始捕捉图像数据 { perror("Fail to ioctl 'VIDIOC_STREAMON'"); exit(EXIT_FAILURE); } return 0; } int mainloop(int fd) { int count = 2; /*8.循环采集图片。*/ while(count-- > 0) { for(;;) { fd_set fds; struct timeval tv; int r; FD_ZERO(&fds);//将指定的文件描述符集清空 FD_SET(fd,&fds);//在文件描述符集合中增加新的文件描述符 /*Timeout*/ tv.tv_sec = 2; tv.tv_usec = 0; r = select(fd + 1,&fds,NULL,NULL,&tv);//判断是否可读(即摄像头是否准备好),tv是定时 if(-1 == r) { if(EINTR == errno) continue; perror("Fail to select"); exit(EXIT_FAILURE); } if(0 == r) { fprintf(stderr,"select Timeout\n"); exit(EXIT_FAILURE); } if(read_frame(fd))//如果可读,执行read_frame ()函数,并跳出循环 break; } } return 0; } //将采集好的数据放到文件中 int process_image(void *addr,int length) { FILE *fp; char name[20]; sprintf(name,"%s%d.jpg",picture_name,num ++); if((fp = fopen(name,"w")) == NULL) { perror("Fail to fopen"); exit(EXIT_FAILURE); } fwrite(addr,length,1,fp); usleep(500); fclose(fp); return 0; } int read_frame(int fd) { struct v4l2_buffer buf; unsigned int i; bzero(&buf,sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; /*9.出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF*/ if(-1 == ioctl(fd,VIDIOC_DQBUF,&buf)) { perror("Fail to ioctl 'VIDIOC_DQBUF'"); exit(EXIT_FAILURE); } assert(buf.index < n_buffer); { #ifdef DEBUG printf ("buf.index dq is %d,\n",buf.index); #endif } //读取进程空间的数据到一个文件中 process_image(user_buf[buf.index].start,user_buf[buf.index].length); /*10.将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF*/ if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))//把数据从缓存中读取出来 { perror("Fail to ioctl 'VIDIOC_QBUF'"); exit(EXIT_FAILURE); } return 1; } void stop_capturing(int fd) { enum v4l2_buf_type type; /*11.停止视频的采集。VIDIOC_STREAMOFF*/ type = V4L2_BUF_TYPE_VIDEO_CAPTURE; if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&type)) { perror("Fail to ioctl 'VIDIOC_STREAMOFF'"); exit(EXIT_FAILURE); } return; } void uninit_camer_device() { unsigned int i; for(i = 0;i < n_buffer;i ++) { if(-1 == munmap(user_buf[i].start,user_buf[i].length)) { exit(EXIT_FAILURE); } } free(user_buf); return; } void close_camer_device(int fd) { if(-1 == close(fd)) { perror("Fail to close fd"); exit(EXIT_FAILURE); } return; } /** * @brief 摄像头拍照函数 * @param void * @retval Nono */ int main(int argc, char* argv[]) { int camera_fd; if(argc == 2 ) { camera_fd = open_camer_device(argv[1]); init_camer_device(camera_fd); start_capturing(camera_fd); num = 0; mainloop(camera_fd); stop_capturing(camera_fd); uninit_camer_device(camera_fd); close_camer_device(camera_fd); printf("Camera get pic success!\n"); } else { printf("Please input video device!\n"); } return 0; }

【usb_camera.h】

#ifndef _USB_CAMERA_H_ #define _USB_CAMERA_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VIDEO_DEV "/dev/video9"//摄像头设备名 typedef struct _buffer { void *start; size_t length; }buffer; int open_camer_device(char * videoDev); int init_mmap(int fd); int init_camer_device(int fd); int start_capturing(int fd); int process_image(void *addr,int length); int read_frame(int fd); int mainloop(int fd); void stop_capturing(int fd); void uninit_camer_device(); void close_camer_device(int fd); void camera_get_image(void); #endif

【Makefile】

ARCH=arm64 CROSS=aarch64-linux-gnu- all: usb_camera sudo scp usb_camera [email protected]:/root usb_camera:usb_camera.c $(CROSS)gcc -o usb_camera usb_camera.c $(CROSS)strip usb_camera clean: @rm -vf usb_camera *.o *~

值得注意的是,Makefile中通过scp将编译好的程序拷贝到开发板,需要根据修改相应开发板的IP地址。当然也可通过其他方式拷贝程序。

5.2.2编译测试

接下来就是编译下载测试了。

1.编译

XzK6f0.md.png

值得注意的是,上面的IP地址是开发板的IP,密码是开发板的登陆密码。

2.测试 接下来在RK3568中运行拍照程序。

XzKgpV.md.png

笔者一次拍两张,当然也可以连续拍很多,在代码中可以修改。最后将照片传到主机查看。

XzK2lT.md.png

这样就是可以查看前面拍得的图片了。

值得注意的是,上面的IP地址是主机的IP,密码是主机开发板的登陆密码。

我们在Windows中查看拍的照片。

XzKR6U.jpg

照片大小在代码中可以调整,可以通过参数传进去。

欢迎访问我的网站

BruceOu的哔哩哔哩BruceOu的主页BruceOu的博客BruceOu的CSDN博客BruceOu的简书BruceOu的知乎

欢迎订阅我的微信公众号

关注公众号[嵌入式实验楼]获取更多资讯



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有